# Author: Simon Oberhammer
# License: MIT (http://www.opensource.org/licenses/mit-license.php)

#stdlib
import imaplib
import sys, os, time, getopt
from time import strftime
import email, email.Errors, email.Parser, email.Header, email.Message, email.Utils

# lib
import markdown2
#custom
from config import *

# ugly global var
parser = email.Parser.Parser()

def error(reason):
    sys.stderr.write('%s\n' % reason)
    sys.exit(1)


def renderSkin(file_path, data):
    # always add this
    data["root-url"] = ROOT_URL
    # open post template and write new file
    template_file = open(file_path,"r")
    content = template_file.read()
    template_file.close()
    for infoKey in data.keys():
        content = content.replace("{{" + infoKey + "}}", data[infoKey])
    return content
    
def createFile(file_path, text):
    print "creating " + file_path
    new_file = open(file_path, "w")
    new_file.write(text)
    new_file.close()
    return

def updateFile(file_path, text, append=False):
    content = ""
    try:
        new_file = open(file_path, "r")
        content = new_file.read()
        new_file.close()
    except IOError:
        print "creating " + file_path
    new_file = open(file_path, "w")
    if append:
        new_file.write(content + text)
    else:
        new_file.write(text + content)
    new_file.close()
    print "updated " + file_path
    return

def createFileFromTemplate(file_path, template_path):
    if not os.path.exists(file_path):
        file_xhtml = renderSkin(template_path, {})
        updateFile(file_path,
                    file_xhtml)
    return


class Message:
    """
    access:
        * data
        * flagSeen()
    """
    def __init__(self, pid, mailbox):
        self.mailbox = mailbox
        self.pid = pid
        typ, dat = self.mailbox.fetch(self.pid,"(UID rfc822)")
        if typ != "OK":
            error(dat[-1])
        addr = ""
        for num in dat[0][0].split():
            message = dat[0][1]
            addr = self.process_attachments(message, num)
        
        # get basic data
        msg_raw = message
        msg = parser.parsestr(msg_raw)
        self.data = {}
        self.data["email-key-id"] = dat[0][0].split(" ")[2]
        self.data["email-date"] = msg["Received"].split(";")[1].strip()
        self.data["email-author"] = addr or msg["From"]
        self.data["email-subject"] = msg["Subject"]
        if not self.data["email-subject"]:
            self.data["email-subject"] = "Untitled"
        self.data["email-key-subject"] = self.data["email-subject"].replace(" ","_").lower()
        self.data["email-key"] = self.data["email-key-subject"] + "-" + self.data["email-key-id"]
        self.payload_data = self._get_payload_data(msg)
        self.data["email-text"] = self.payload_data
        self.data["email-xhtml"] = markdown2.markdown(self.payload_data)
        # payload

    def process_attachments(self, text, msgnum):
        try:
            msg = email.message_from_string(text)
        except email.Errors.MessageError, val:
            error("Message %s parse error: %s" % (msgnum, str(val)))

        date = msg['Date'] or 'Thu, 17 Jan 1981 12:02:27 +1000'
        #date = time.strftime('%Y_%m_%d.%T', email.Utils.parsedate(date))
        addr = email.Utils.parseaddr(msg['From'])[1]
        attachments_found = self.process_msg_parts(msg, addr, date, 0, msgnum)
        return addr
    
    def gen_filename(self,name, mtyp, addr, date, n):
        path = os.path.join(ATTACHMENTS_PATH, name)
        print "Saving attachment  " + path + "  from  " + addr
        return path

    
    # see http://code.activestate.com/recipes/498189/
    def process_msg_parts(self,msg, addr, date, count, msgnum):
        for part in msg.walk():
            if part.is_multipart():
                continue
            filename = None
            dtypes = part.get_params(None, 'Content-Disposition')
            if not dtypes:
                if part.get_content_type() == 'text/plain':
                    continue
                ctypes = part.get_params()
                if not ctypes:
                    continue
                for key,val in ctypes:
                    if key.lower() == 'name':
                        filename = self.gen_filename(val, part.get_content_type(), addr, date, count)
                        break
                    else:
				        continue
            else:
                attachment,filename = None,None
                for key,val in dtypes:
                    key = key.lower()
                    if key == 'filename':
                        filename = val
                    if key == 'attachment':
                        attachment = 1
                if not attachment:
                    continue
                filename = self.gen_filename(filename, part.get_content_type(), addr, date, count)
			
            try:
                data = part.get_payload(decode=1)
            except:
                typ, val = sys.exc_info()[:2]
                # FIXME warn
                error("Message %s attachment decode error: %s for %s ``%s''" % (msgnum, str(val), part.get_content_type(), filename))
                continue

            if not data:
                # FIXME warn
                error("Could not decode attachment %s for %s" % (part.get_content_type(), filename))
                continue

            if type(data) is type(msg):
                count = walk_parts(data, addr, date, count, msgnum)
                continue
		
            if ATTACHMENTS_SAVE and filename:
                exists = "0"
                try:
                    curdir= os.getcwd()
                    fd = open(filename, "wb")	
                    fd.write(data)
                    fd.close()
                    os.chdir(curdir)
                except IOError, val:
                    error('Could not create "%s": %s' % (filename, str(val)))
            count += 1
        return count


    def _get_payload_data(self, msg):
        payloads = msg.get_payload()
        # FIXME is this buggy? checkout imap rfc
        if type(payloads) == list:
            payload_data = payloads[0].get_payload()
        else:
            payload_data = payloads
        return payload_data

    def flagSeen(self):
        self.mailbox.store(self.pid, "+FLAGS", "\\Seen")

class Mailbox:
    def __init__(self):
        self.m = imaplib.IMAP4_SSL(IMAP_SSL_SERVER)
        try:
	        typ,dat = self.m.login(IMAP_USER,IMAP_PWD)
        except:
	        typ,dat = sys.exc_info()[:2]
        if typ != 'OK':
            error('Could not open INBOX for "%s" on "%s": %s' % (IMAP_USER, IMAP_SSL_SERVER, str(dat)))

        self.m.select()

    def getMessages(self,filter_string):
        emails = []
        typ, dat = self.m.search(None,filter_string)
        for num in dat[0].split():
            emails.append(Message(num, self.m))
        
        
        #email_ids = email_info[1][0].split(" ")
        #if not len(email_ids) or email_ids[0] == "":
        #    print "no new msgs in " + filter_string
        #    return []
        #emails = []
        #for eid in email_ids:
            
        return emails

    def close(self):
        self.m.close()
        self.m.logout()

class Mailog:
    def __init__(self, mailbox):
        self.mailbox = mailbox

    def process(self):
        self.processIncomingPosts()
        self.processIncomingComments()
        return

    def processIncomingPosts(self):
        post_emails = self.mailbox.getMessages("(TO '+post') (NOT SEEN)")
        for pemail in post_emails:
            # create post-x.html
            post_xhtml = renderSkin(TEMPLATES_PATH + "post-content.html", 
                                      pemail.data)
            createFile(HTDOCS_PATH + "posts" + os.sep + pemail.data["email-key"] + ".html",
                        post_xhtml)
    
            # update postlist.html
            postlist_item_xhtml = renderSkin(TEMPLATES_PATH + "postlist-item.html",
                                                pemail.data)            
            updateFile(HTDOCS_PATH + "postlist.html",
                       postlist_item_xhtml)
            
            # create comment list file with post-comment link
            comment_header_xhtml = renderSkin(TEMPLATES_PATH + "commentlist-header.html",
                                         pemail.data)
            updateFile(HTDOCS_PATH + "comments" + os.sep + pemail.data["email-key"] + ".html",
                        comment_header_xhtml)

            # update rss 
            rss_file = HTDOCS_PATH + "postlist.html"
            createFileFromTemplate(rss_file, TEMPLATES_PATH + "rss-header.html",)
            rss_item_xhtml = renderSkin(TEMPLATES_PATH + "rss-item.html",
                                        pemail.data)
            updateFile(HTDOCS_PATH + "rss.html",
                        rss_item_xhtml,
                        append = True)
            pemail.flagSeen()
        return

    def processIncomingComments(self):
        comment_emails = self.mailbox.getMessages("(TO '+comment') (NOT SEEN)")
        for cemail in comment_emails:
            comment_xhtml = renderSkin(TEMPLATES_PATH + "comment-content.html",
                                         cemail.data)
            # filename for comment is the posts key which is encoded in the subject
            comment_file_path = cemail.data["email-subject"].split("$$")[0].strip()
            updateFile(HTDOCS_PATH + "comments" + os.sep + comment_file_path + ".html",
                        comment_xhtml)
            cemail.flagSeen()
        return
        
######### MAIN

# helper
def nukedir(directory):
    if directory[-1] == os.sep: directory = directory[:-1]
    files = os.listdir(directory)
    for file_path in files:
        if file_path == '.' or file_path == '..': continue
        # FIXME danger special rule, never delete firstpost
        path = directory + os.sep + file_path
        if "firstpost" in file_path or ("." == file_path[0]):
            continue
        if os.path.isdir(path):
            nukedir(path)
        else:
            os.unlink(path)
            print "removed file " + str(file_path)
    return

def process():
    print "reading your email"
    mailbox = Mailbox()
    mailog = Mailog(mailbox)
    mailog.process()
    mailbox.close()

def main(argv=None):
    if argv is None:
        argv = sys.argv
    if len(argv) > 1:
        if argv[1] == "--reset":
            # empty directories
            print "EMPTYING HTDOCS"
            print "---------------"
            print "PRESS CTRL + C TO CANCEL"
            print "press enter to continue"
            value = raw_input()
            nukedir(HTDOCS_PATH + "comments" + os.sep)
            nukedir(HTDOCS_PATH + "posts" + os.sep)
            nukedir(ATTACHMENTS_PATH)
            try:
                os.unlink(HTDOCS_PATH + "postlist.html")
                print "removed postlist.html"
            except:
                # FIXME
                pass
            try:
                os.unlink(HTDOCS_PATH + "rss.html")
                print "removed rss.html"
            except:
                # FIXME
                pass

        elif argv[1] == "--daemon":
            while True:
                process()
                time.sleep(30)
        elif argv[1] == "--help":
            print "withouth options mailog will try to fetch and process the configured emails once"
            print "possible options:"
            print "  --daemon"
            print "  --reset (DANGER) to reset your htdocs"
            print "-------------"

    else:
        print "--help for help"
        process()
#    print "reading your email"
#    mailbox = Mailbox()
#    mailog = Mailog(mailbox)
#    mailog.process()

if __name__ == "__main__":
    sys.exit(main())


